package top.healthylife.docx4jhelper.utils;

import cn.hutool.core.date.SystemClock;
import cn.hutool.core.date.TimeInterval;
import cn.hutool.core.util.IdUtil;
import cn.hutool.http.HttpUtil;
import io.swagger.annotations.ApiModel;
import top.healthylife.docx4jhelper.base.Docx4jBaseTpl;
import top.healthylife.docx4jhelper.enums.Docx4jStyle;
import lombok.Data;
import lombok.SneakyThrows;
import lombok.experimental.Accessors;
import lombok.extern.slf4j.Slf4j;
import org.docx4j.Docx4J;
import org.docx4j.convert.out.HTMLSettings;
import org.docx4j.fonts.IdentityPlusMapper;
import org.docx4j.fonts.Mapper;
import org.docx4j.fonts.PhysicalFont;
import org.docx4j.fonts.PhysicalFonts;
import org.docx4j.jaxb.Context;
import org.docx4j.model.structure.PageSizePaper;
import org.docx4j.openpackaging.exceptions.Docx4JException;
import org.docx4j.openpackaging.exceptions.InvalidFormatException;
import org.docx4j.openpackaging.packages.WordprocessingMLPackage;
import org.docx4j.openpackaging.parts.WordprocessingML.MainDocumentPart;
import org.docx4j.toc.Toc;
import org.docx4j.toc.TocGenerator;
import org.docx4j.toc.TocHelper;
import org.docx4j.wml.*;

import java.awt.*;
import java.io.*;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * @author : GaoZeXi
 * @date 2022/1/25 9:39
 */
@Data
@Accessors(chain = true)
@ApiModel("WordProcessor")
@Slf4j
@SuppressWarnings("all")
public class WordProcessor {

    static Mapper fontMapper = new IdentityPlusMapper();
    static Map<String, PhysicalFont> systemFontMapper = new HashMap<>();

    static {
        TimeInterval timeInterval = new TimeInterval();
        Font[] allFonts = GraphicsEnvironment.getLocalGraphicsEnvironment().getAllFonts();
        for (Font allFont : allFonts) {
            String psName = allFont.getPSName();
            systemFontMapper.put(allFont.getFamily(), PhysicalFonts.get(psName));
            systemFontMapper.put("隶书", PhysicalFonts.get("LiSu"));
            systemFontMapper.put("宋体", PhysicalFonts.get("SimSun"));
            systemFontMapper.put("微软雅黑", PhysicalFonts.get("Microsoft Yahei"));
            systemFontMapper.put("黑体", PhysicalFonts.get("SimHei"));
            systemFontMapper.put("楷体", PhysicalFonts.get("KaiTi"));
            systemFontMapper.put("新宋体", PhysicalFonts.get("NSimSun"));
            systemFontMapper.put("华文行楷", PhysicalFonts.get("STXingkai"));
            systemFontMapper.put("华文仿宋", PhysicalFonts.get("STFangsong"));
            systemFontMapper.put("宋体扩展", PhysicalFonts.get("simsun-extB"));
            systemFontMapper.put("仿宋", PhysicalFonts.get("FangSong"));
            systemFontMapper.put("仿宋_GB2312", PhysicalFonts.get("FangSong_GB2312"));
            systemFontMapper.put("幼圆", PhysicalFonts.get("YouYuan"));
            systemFontMapper.put("华文宋体", PhysicalFonts.get("STSong"));
            systemFontMapper.put("华文中宋", PhysicalFonts.get("STZhongsong"));
            systemFontMapper.put("宋体正文", PhysicalFonts.get("SimSun"));
            //解决宋体（正文）和宋体（标题）的乱码问题
            PhysicalFonts.put("PMingLiU", PhysicalFonts.get("SimSun"));
            PhysicalFonts.put("新細明體", PhysicalFonts.get("SimSun"));
        }
        log.info("加载字体文件耗时:{}ms", timeInterval.intervalMs());
    }

    protected static void reloadFontMapper() {
        //加载系统字体。
        TimeInterval timeInterval = new TimeInterval();
        systemFontMapper.keySet().forEach(e -> fontMapper.put(e, systemFontMapper.get(e)));

        log.info("重载字体文件耗时:{}ms", timeInterval.intervalMs());
    }

    /**
     * 从本地文件或网络文件加载
     *
     * @param filePathOrUrl
     * @return DocumentLoader
     */
    @SneakyThrows
    public static DocumentLoader loadFile(String filePathOrUrl) {
        InputStream inputStream = null;
        if (filePathOrUrl.startsWith("http")) {
            byte[] bytes = HttpUtil.downloadBytes(filePathOrUrl);
            inputStream = new ByteArrayInputStream(bytes);
            //获取fileName
            String filename = filePathOrUrl.substring(filePathOrUrl.lastIndexOf('/') + 1);
            DocumentLoader documentLoader = loadFile(inputStream);
            documentLoader.setFileName(filename);
            return documentLoader;
        } else {
            File file = new File(filePathOrUrl);
            return loadFile(file);
        }
    }

    /**
     * 从本地文件加载
     *
     * @param file
     * @return
     */
    @SneakyThrows
    public static DocumentLoader loadFile(File file) {
        String fileName = file.getName();
        int index = fileName.lastIndexOf(".");
        String extension = fileName.substring(index + 1);
        if (!"docx".equals(extension)) {
            throw new RuntimeException("只支持docx类型");
        }
        DocumentLoader documentLoader = loadFile(new FileInputStream(file));
        documentLoader.setFileName(fileName);
        return documentLoader;
    }

    /**
     * 从文件流加载
     *
     * @param is
     * @return
     */
    @SneakyThrows
    public static DocumentLoader loadFile(InputStream is) {
        WordprocessingMLPackage load = WordprocessingMLPackage.load(is);
        //reloadFontMapper();
        load.setFontMapper(fontMapper);
        DocumentLoader documentLoader = new DocumentLoader(load);
        RFonts rFonts = documentLoader.factory.createRFonts();
        rFonts.setAsciiTheme(null);
        //默认使用微软雅黑转pdf
        rFonts.setAscii("微软雅黑");
        documentLoader.load.getMainDocumentPart().getPropertyResolver().getDocumentDefaultRPr().setRFonts(rFonts);
        return documentLoader;
    }


    public static DocumentBuilder startDocument() {
        return new DocumentBuilder();
    }


    public static class DocumentLoader {
        private String fileName;
        private final WordprocessingMLPackage load;
        private final ObjectFactory factory = Context.getWmlObjectFactory();

        public DocumentLoader(WordprocessingMLPackage load) {
            this.load = load;
        }

        public String getFileName() {
            return fileName;
        }

        public void setFileName(String fileName) {
            this.fileName = fileName;
        }

        /**
         * 变量替换
         *
         * @param docx4jBaseTpl 书签模板
         * @return DocumentLoader 文档加载器
         */
        public DocumentLoader replaceVar(Docx4jBaseTpl docx4jBaseTpl) {
            WordFileUtil.docx4jVariableParse(this.load, docx4jBaseTpl);
            return this;
        }

        /**
         * 另存为
         *
         * @param filePath 文件路径
         * @return DocumentLoader 文档加载器
         */
        public DocumentLoader saveDocx(String filePath) throws Docx4JException {
            File file = new File(filePath);
            load.save(file);
            return this;
        }

        /**
         * 另存为
         *
         * @param file 文件
         * @return DocumentLoader 文档加载器
         */
        public DocumentLoader saveDocx(File file) throws Docx4JException {
            load.save(file);
            return this;
        }

        /**
         * 另存为输出流
         *
         * @param os 输出流
         * @return DocumentLoader 文档加载器
         */
        @SneakyThrows
        public DocumentLoader saveDocx(OutputStream os) {
            load.save(os);
            return this;
        }

        @SneakyThrows
        public DocumentLoader saveDocx(OutputStream os, String password) {
            load.save(os, Docx4J.FLAG_SAVE_ENCRYPTED_AGILE, password);
            return this;
        }

        @SneakyThrows
        public void toPdf(String filePath) {
            File file = new File(filePath);
            toPdf(new FileOutputStream(file));
        }

        @SneakyThrows
        public void toPdf(File pdfFile) {
            log.info("准备转换为PDF文件");
            long now = SystemClock.now();
            toPdf(new FileOutputStream(pdfFile));
            log.info("转换完成,耗时：{}ms", SystemClock.now() - now);
        }

        /**
         * @param os 输出流
         */
        @SneakyThrows
        public void toPdf(OutputStream os) {
            //StartEvent startEvent = new StartEvent( load, WellKnownProcessSteps.PDF );
            //startEvent.publish();
            //
            //FOSettings settings = new FOSettings();
            //settings.setWmlPackage(load);
            //settings.setApacheFopMime("application/pdf");
            //toFO(settings, os, FLAG_NONE);
            //new EventFinished(startEvent).publish();
            //FopFactory build = FORendererApacheFOP.getFopFactoryBuilder(settings).build();
            //FOUserAgent foUserAgent = FORendererApacheFOP.getFOUserAgent(settings, build);
            Docx4J.toPDF(load, os);
        }

        @SneakyThrows
        public void toHtml(String filePath) {
            toHtml(new File(filePath));
        }

        @SneakyThrows
        public void toHtml(File file) {
            toHtml(new FileOutputStream(file));
        }

        @SneakyThrows
        public void toHtml(OutputStream os) {
            HTMLSettings htmlSettings = Docx4J.createHTMLSettings();
            htmlSettings.setWmlPackage(load);
            Docx4J.toHTML(htmlSettings, os, Docx4J.FLAG_EXPORT_PREFER_NONXSL);
        }

    }

    /**
     * 文档构建器
     */
    public static class DocumentBuilder {
        WordprocessingMLPackage mlPackage;
        ObjectFactory factory = Context.getWmlObjectFactory();
        private static TocGenerator tocGenerator;

        public WordprocessingMLPackage getMlPackage() {
            return mlPackage;
        }

        public void setMlPackage(WordprocessingMLPackage mlPackage) {
            this.mlPackage = mlPackage;
        }

        public DocumentBuilder() {
            try {
                this.mlPackage = WordprocessingMLPackage.createPackage(PageSizePaper.valueOf("A4"), false);
            } catch (InvalidFormatException e) {
                throw new RuntimeException("创建文档异常(" + e.getMessage() + ")");
            }
        }

        public DocumentBuilder(WordprocessingMLPackage mlPackage) {
            this.mlPackage = mlPackage;
        }

        public DocumentBuilder addParagraph(String content) {
            //默认开启首行缩进
            return addParagraph(content, true);
        }

        public DocumentBuilder addParagraph(String content, JcEnumeration align) {
            //默认开启首行缩进
            return addParagraph(content, true, align);
        }


        /**
         * 添加1行空行
         *
         * @return
         */
        public DocumentBuilder addBlankParagraph() {
            return addBlankParagraph(1);
        }

        /**
         * 添加多行空行
         *
         * @return
         */
        public DocumentBuilder addBlankParagraph(int size) {
            for (int i = 0; i < size; i++) {
                addParagraph("", false);
            }
            return this;
        }

        public DocumentBuilder addParagraph(String content, boolean indentation) {
            return addParagraph(content, indentation, JcEnumeration.LEFT);
        }

        public DocumentBuilder addParagraph(String content, boolean indentation, JcEnumeration align) {
            P p = WordFileUtil.createP(content);
            if (indentation) {
                PPr pPr = p.getPPr();
                if (pPr == null) {
                    pPr = factory.createPPr();
                }
                PPrBase.Ind pPrBaseInd = factory.createPPrBaseInd();
                pPrBaseInd.setFirstLineChars(BigInteger.valueOf(200));
                pPrBaseInd.setFirstLine(BigInteger.valueOf(200));
                pPr.setInd(pPrBaseInd);
                p.setPPr(pPr);
            }
            Docx4jUtil.setPAlign(p, align);
            addElement(p);
            return this;
        }

        public DocumentBuilder addStyledParagraph(Docx4jStyle style, String content) {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            mainDocumentPart.addStyledParagraphOfText(style.name(), content);
            return this;
        }

        public DocumentBuilder pageFooter(String content, HdrFtrRef scope) {
            SectPr sectPr = Docx4jUtil.getSectPr(mlPackage);
            Docx4jUtil.createFooter(content, sectPr, scope, mlPackage);
            return this;
        }

        /**
         * 使用默认TOC指令生成目录，并将其添加到文档的开头。要更改TOC标题，请使用TOC . settocheadingtext
         *
         * @param enable
         * @return
         */
        @SneakyThrows
        public DocumentBuilder toc(String tocHeader) {
            return toc(tocHeader, getCurrentIndex());
        }

        @SneakyThrows
        public DocumentBuilder toc(String tocHeader, int index) {
            TocGenerator tocGenerator = new TocGenerator(mlPackage);
            Toc.setTocHeadingText(tocHeader);
            // 添加标题
            tocGenerator.generateToc(index, TocHelper.DEFAULT_TOC_INSTRUCTION, true);
            this.tocGenerator = tocGenerator;
            return this;
        }

        /**
         * 页眉
         *
         * @param context
         * @return
         */
        @SneakyThrows
        public DocumentBuilder pageHeader(String context) {
            return pageHeader(context, HdrFtrRef.DEFAULT, false);
        }

        /**
         * 页眉,同时使用该内容作为文章水印
         *
         * @param context
         * @param waterMark
         * @return
         */
        @SneakyThrows
        public DocumentBuilder pageHeader(String context, HdrFtrRef scope, boolean waterMark) {
            ObjectFactory factory = Context.getWmlObjectFactory();
            SectPr sectPr = Docx4jUtil.getSectPr(mlPackage);
            Docx4jUtil.createHeader(context, waterMark, sectPr, scope, mlPackage);
            return this;
        }

        /**
         * 当前文档元素索引处添加新页
         *
         * @param index 在哪个元素位置
         * @return
         */
        public DocumentBuilder addNewPage() {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            List<Object> content = mainDocumentPart.getContent();

            P p = WordFileUtil.createP("");
            Docx4jUtil.addPageBreak(p, STBrType.PAGE);
            addElement(p);
            return this;
        }

        private String getBlankStr() {
            char blank = 8203;
            char[] blankArr = new char[1];
            blankArr[0] = blank;
            return new String(blankArr);
        }

        /**
         * 指定位置添加新页
         *
         * @param index 在哪个元素位置
         * @return
         */
        public DocumentBuilder addNewPage(int index) {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            List<Object> content = mainDocumentPart.getContent();
            P p = WordFileUtil.createP();
            Docx4jUtil.addPageBreak(p, STBrType.PAGE);
            addElementByIndex(p, index);
            return this;
        }

        /**
         * 新增标题属性
         *
         * @param headerText
         * @return
         * @apiNote 该方法存在已知问题, 使用了自定义的样式
         */
        public DocumentBuilder title(String headerText) {
            return title(headerText, 26, getCurrentIndex());
        }

        public DocumentBuilder title(String headerText, Integer fontSize) {
            return title(headerText, fontSize, getCurrentIndex());
        }

        /**
         * 指定索引处添加
         *
         * @param headerText
         * @param index
         * @return
         */
        public DocumentBuilder title(String headerText, Integer fontSize, Integer index) {
            try {
                // create title
                MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();

                //P p = mainDocumentPart.createStyledParagraphOfText(Docx4jStyle.Title.name(), headerText);
                P p = WordFileUtil.createPWithStyle(headerText, fontSize);
                WordFileUtil.setPAlign(p, JcEnumeration.CENTER);
                addElementByIndex(p, index);
            } catch (Exception e) {
                throw new RuntimeException("文档标题添加失败:" + e.getMessage());
            }
            return this;
        }

        /**
         * 插入静态表格,已知字段列表
         *
         * @param dataList
         * @param <T>
         * @return
         */
        public <T> DocumentBuilder addStaticDataTable(List<T> dataList) {
           return addStaticDataTable(dataList,getCurrentIndex());
        }


        /**
         * 插入静态表格,已知字段列表
         *
         * @param dataList
         * @param <T>
         * @return
         */
        public <T> DocumentBuilder addStaticDataTable(List<T> dataList,Integer index) {
            try {
                Tbl tableAndInitData = Docx4jUtil.createTableAndInitData(mlPackage, dataList);
                addElementByIndex(tableAndInitData,index);
            } catch (Exception e) {
                throw new RuntimeException("表格渲染失败:" + e.getMessage());
            }
            return this;
        }

        /**
         * 插入动态表格(未知的字段列表),表格的标题为map的key列表
         *
         * @param dataList
         * @return
         */
        public DocumentBuilder addDynamicDataTable(List<LinkedHashMap<String, Object>> dataList) {
             return addDynamicDataTable(dataList,getCurrentIndex());
        }

        /**
         * 插入动态表格(未知的字段列表),表格的标题为map的key列表
         *
         * @param dataList
         * @return
         */
        public DocumentBuilder addDynamicDataTable(List<LinkedHashMap<String, Object>> dataList,Integer index) {
            try {
                Tbl tableAndInitData = Docx4jUtil.createTableAndInitDataByMap(mlPackage, dataList);
                addElementByIndex(tableAndInitData,index);
            } catch (Exception e) {
                throw new RuntimeException("表格渲染失败:" + e.getMessage());
            }
            return this;
        }


        @SneakyThrows
        public DocumentBuilder addImage(String filePath, String altText) {
            return addImage(filePath, altText, getCurrentIndex());
        }

        @SneakyThrows
        public DocumentBuilder addImage(String filePath, String altText, int index) {
            Drawing imagePart = Docx4jUtil.createImagePart(mlPackage, filePath, altText);
            P p = factory.createP();
            org.docx4j.wml.R run = factory.createR();
            run.getContent().add(imagePart);
            p.getContent().add(run);
            addElementByIndex(p, index);
            return this;
        }

        public DocumentBuilder addHtml(String htmlStr) {
            return addHtml(htmlStr, "docx4j-htmlchunk-" + IdUtil.fastSimpleUUID(), getCurrentIndex());
        }

        @SneakyThrows
        public DocumentBuilder addHtml(String htmlStr, String htmlPartName, int index) {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            CTAltChunk ctAltChunk = Docx4jUtil.creatHtmlChunk(mlPackage, htmlStr, htmlPartName);
            addElementByIndex(ctAltChunk, index);
            return this;
        }


        private void addElement(Object obj) {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            mainDocumentPart.addObject(obj);
        }


        private void addElementByIndex(Object obj, int index) {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            List<Object> content = mainDocumentPart.getContent();
            if (index < 0 || index > content.size()) {
                throw new RuntimeException("索引设置错误");
            }
            content.add(index, obj);
        }

        public int getCurrentIndex() {
            MainDocumentPart mainDocumentPart = mlPackage.getMainDocumentPart();
            List<Object> content = mainDocumentPart.getContent();
            return content.size();
        }

        @SneakyThrows
        public void builder(String filePath) {
            if (this.tocGenerator != null) {
                tocGenerator.updateToc();
            }
            mlPackage.save(new File(filePath));
        }

        @SneakyThrows
        public void builder(File file) {
            if (this.tocGenerator != null) {
                tocGenerator.updateToc(mlPackage, true);
            }
            mlPackage.save(file);
        }

        @SneakyThrows
        public void builder(OutputStream os) {
            if (this.tocGenerator != null) {
                tocGenerator.updateToc(mlPackage, true);
            }
            mlPackage.save(os);
        }

    }


}
